package pkg.andru.database;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import pkg.andru.util.Log;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;


/**
 * this class bind the table in the dataprovider.</br> one bean -- one manager
 * -- one table</br>use the java simple bean specification to bind the bean to
 * the table.</p> <b>WARN:current implements DO NOT support all the javabean
 * spec and NOT all object type, also the IDE auto finish the getter and setter
 * method has different with java bean spec. ONLY support {@link String}, int,
 * {@link Date}, if you have boolean value, use int instead of.</b></p>
 * 
 * @author idiottiger
 * @version 1.0
 * @param <T>
 */

public class BaseDataManager<T extends Object> {

    public static final String LOG_TAG = "BaseManager";

    public static final String SET_METHOD_HEAD = "set", GET_METHOD_HEAD = "get";

    public static final int MODIFIER_P_S_F = (Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL);

    static final DateFormat sDataFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), sDataFormatter2 = new SimpleDateFormat("yyyy-MM-dd");

    static final int SET_TYPE = 1, GET_TYPE = 2;

    private Date defaultDate;

    private Uri contentUri;

    // save the fields
    private Field[] fields;

    // save the methods
    private Method[] methods;

    // make the reslover open here
    ContentResolver reslover;

    /**
     * only sub class can see it
     * 
     * @param con
     * @param uri
     */
    public BaseDataManager(Context con, Uri uri) {
        if (con == null || uri == null) {
            throw new NullPointerException("contenxt or uri is null.");
        }
        reslover = con.getContentResolver();
        contentUri = uri;
    }

    protected boolean insertRecord(T type) {
        if (type == null) {
            Log.e(LOG_TAG, "the record you want to insert is null");
            return false;
        }
        ContentValues values = extraValuesFromType(type);
        if (values == null || values.size() == 0) {
            Log.e(LOG_TAG, "the record you want to insert is empty");
            return false;
        }
        return (reslover.insert(contentUri, values) != null);
    }

    protected boolean insertRecord(T type, String[] filter) {
        if (type == null) {
            Log.e(LOG_TAG, "the record you want to insert is null");
            return false;
        }
        ContentValues values = extraValuesFromType(type, filter);
        if (values == null || values.size() == 0) {
            Log.e(LOG_TAG, "the record you want to insert is empty");
            return false;
        }
        return (reslover.insert(contentUri, values) != null);
    }

    protected boolean insertRecords(T[] types) {
        if (types == null || types.length == 0) {
            return false;
        }
        int size = types.length;
        ContentValues[] values = new ContentValues[size];
        for (int i = 0; i < size; i++) {
            if (types[i] != null) {
                values[i] = extraValuesFromType(types[i]);
            }
        }
        return (reslover.bulkInsert(contentUri, values) > 0);
    }

    protected boolean removeRecord(String where, String args[]) {
        if (where == null || where.trim().length() == 0) {
            Log.e(LOG_TAG, "the remove condition is null or empty");
            return false;
        }
        return (reslover.delete(contentUri, where, args) > 0);
    }

    protected boolean removeAllRecords() {
        return reslover.delete(contentUri, null, null) > 0;
    }

    protected boolean updateRecord(T type, String[] filter, String selection, String[] selectionArgs) {
        ContentValues values = extraValuesFromType(type, filter);
        if (values != null) {
            return reslover.update(contentUri, values, selection, selectionArgs) > 0;
        }
        return false;
    }

    protected boolean updateRecords() {
        Log.w(LOG_TAG, "dont implements here");
        return true;
    }

    protected List<T> query(Class<T> tclass, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        T type = null;
        try {
            type = tclass.newInstance();
        } catch (Exception e) {
            Log.e(LOG_TAG, tclass + " create instance error");
            return null;
        }
        return query(type, projection, selection, selectionArgs, sortOrder);
    }

    private List<T> query(T type, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        Cursor cursor = reslover.query(contentUri, projection, selection, selectionArgs, sortOrder);
        if (cursor != null) {
            Log.d(LOG_TAG, "get record:" + cursor.getCount());
            List<T> lists = null;
            if (cursor.getCount() != 0) {
                lists = extraTypeFromCursor(type, cursor);
            }
            if (cursor != null) {
                cursor.close();
                cursor = null;
            }
            return lists;
        }

        return null;
    }

    /*
     * reflection method to extra the value and set to the content values
     */
    private ContentValues extraValuesFromType(T type) {
        initFieldsAndMethods(type);
        ContentValues values = new ContentValues();
        if (fields != null && methods != null) {
            int size = fields.length;
            for (int i = 0; i < size; i++) {
                if (methods[i] == null) {
                    continue;
                }
                Object result = null;
                Class<? extends Object> resultType = null;
                try {
                    result = methods[i].invoke(type, new Object[] {});
                    resultType = methods[i].getReturnType();
                } catch (Exception e) {
                    Log.e(LOG_TAG, "the method:" + methods[i].getName() + " invoke error");
                }
                // here we should make the data to a default value
                if (result == null && resultType == Date.class) {
                    result = getDefaultDate();
                }
                if (result != null && resultType != null) {
                    if (resultType == int.class) {
                        values.put(fields[i].getName(), (Integer) result);
                    } else if (resultType == String.class) {
                        values.put(fields[i].getName(), (String) result);
                    } else if (resultType == Date.class) {
                        values.put(fields[i].getName(), dateConvertToString((Date) result));
                    }
                }
            }
        }
        return values;
    }

    /*
     * add filter is want to escape the string ,dont get the value
     */
    private ContentValues extraValuesFromType(T type, String[] filter) {
        ContentValues values = extraValuesFromType(type);
        if (values != null && filter != null && filter.length != 0) {
            for (String str : filter) {
                values.remove(str);
            }
        }
        return values;
    }

    /*
     * init the fields and methods one filed to two methods , set and get method
     */
    private void initFieldsAndMethods(T type) {
        if (fields != null && methods != null) {
            return;
        }
        Class<?> tclass = type.getClass();
        // some bean has static final vars, so using extraPrivateField method
        fields = extraPrivateField(tclass);
        if (fields != null && fields.length != 0) {
            int counts = fields.length;
            methods = new Method[counts * 2];
            for (int j = 0; j < counts; j++) {
                Method getMethod = null, setMethod = null;
                try {
                    getMethod = tclass.getDeclaredMethod(getGetMethodName(fields[j]), new Class[] {});
                    setMethod = tclass.getDeclaredMethod(getSetMethodName(fields[j]), fields[j].getType());
                    methods[j] = getMethod;
                    methods[j + counts] = setMethod;
                } catch (SecurityException e) {
                    Log.e(LOG_TAG, "dont have permisson to access the:" + type.getClass().getName());
                } catch (NoSuchMethodException e) {
                    Log.e(LOG_TAG, "dont have this method:" + e.getMessage());
                }
            }
        }
    }

    private String getGetMethodName(Field field) {
        return getMethodName(GET_TYPE, field);
    }

    private String getSetMethodName(Field field) {
        return getMethodName(SET_TYPE, field);
    }

    private String getMethodName(int type, Field field) {
        String upper = Character.toUpperCase(field.getName().charAt(0)) + "";
        String methodTypeStr = (type == SET_TYPE) ? SET_METHOD_HEAD : GET_METHOD_HEAD;
        String methodName = methodTypeStr + upper + field.getName().substring(1);
        return methodName;
    }

    /**
     * <br>
     * Just extra the private fields, skip the static final vars.</br>
     */
    private Field[] extraPrivateField(Class<?> tclass) {
        ArrayList<Field> fields = new ArrayList<Field>();
        Field[] alls = tclass.getDeclaredFields();
        if (alls == null || alls.length == 0) {
            return null;
        }
        int size = alls.length;
        for (int i = 0; i < size; i++) {
            if (alls[i].getModifiers() != MODIFIER_P_S_F) {
                fields.add(alls[i]);
            }
        }
        Field[] returnfields = new Field[fields.size()];
        return fields.toArray(returnfields);
    }

    @SuppressWarnings("unchecked")
    private List<T> extraTypeFromCursor(T type, Cursor cursor) {
        initFieldsAndMethods(type);
        if (fields != null && cursor != null) {
            int index = 0, size = fields.length, cursorcount = cursor.getCount();
            List<T> lists = new ArrayList<T>(cursorcount);
            String name = null;
            for (int j = 0; j < cursorcount; j++) {
                cursor.moveToNext();
                Object obj = null;
                try {
                    obj = type.getClass().newInstance();
                } catch (Exception e1) {
                    Log.e(LOG_TAG, type.getClass() + " init error");
                }
                for (int i = 0; i < size; i++) {
                    name = fields[i].getName();
                    index = cursor.getColumnIndexOrThrow(name);
                    if (methods[i + size] != null) {
                        try {
                            Class<? extends Object> tclass = fields[i].getType();
                            if (tclass == int.class) {
                                methods[i + size].invoke(obj, cursor.getInt(index));
                            } else if (tclass == String.class) {
                                if (cursor.getString(index) != null) {
                                    methods[i + size].invoke(obj, cursor.getString(index));
                                }
                            } else if (tclass == Date.class) {
                                if (cursor.getString(index) != null) {
                                    methods[i + size].invoke(obj, stringConverToDate(cursor.getString(index)));
                                } else {
                                    // if no value,may be set to the default
                                    // date
                                    methods[i + size].invoke(obj, defaultDate);
                                }
                            }
                        } catch (Exception e) {
                            Log.e(LOG_TAG, methods[i + size].getName() + " invoke error.");
                        }

                    }
                }
                lists.add((T) obj);
            }
            return lists;
        }
        return null;
    }

    private Date stringConverToDate(String str) {
        try {
            return sDataFormatter.parse(str);
        } catch (ParseException e) {
            try {
                return sDataFormatter2.parse(str);
            } catch (ParseException e1) {
                Log.e(LOG_TAG, "string conver to date error.");
                return null;
            }
        }
    }

    private String dateConvertToString(Date date) {
        String str = sDataFormatter.format(date);
        if (str == null) {
            str = sDataFormatter2.format(date);
        }
        return str;
    }

    private Date getDefaultDate() {
        if (defaultDate == null) {
            Calendar cal = Calendar.getInstance();
            cal.set(1900, 0, 1, 0, 0, 0);
            defaultDate = cal.getTime();
        }
        return defaultDate;
    }

}
